package main

import (
	"ftpProject/library/utils"
	"github.com/gogf/gf/container/garray"
	"github.com/gogf/gf/errors/gerror"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/os/gcfg"
	"github.com/gogf/gf/util/gconv"
	"io"
	"math"
	"os"
	"fmt"
	"net"
	"path"
	"bytes"
	"strconv"
	"strings"
	"runtime"
	"path/filepath"
)

type FTP struct {
	host     string
	port     int
	user     string
	passwd   string
	pasv     int
	cmd      string
	Code     int
	Message  string
	Debug    bool
	stream   []byte
	conn     net.Conn
	orderId  int 	// 编号id
	workState string 	// 工作状态 err:为有失败
	failCount    int 	// 失败总数
	count    int 	// 工作总数
	StateStr string // 状态字符串
	totalSize	int64 // 当前ftp文件总大小
	putPath	 string // 上传的路径
	ftpPath	 string // ftp字符串内容
}

type Config struct {
	Host      string // ip
	Port      int    // 端口
	User      string // 用户名
	Passwd    string // 密码
	LocalDir  string // 本地位置
	LocalFile string // 本地文件
	FtpDir    string // ftp位置
	CpuDouble float64	// cpu倍数
}

var config Config

var fileBuffer bytes.Buffer
var ftpTatleNum = 0 		// 上传总数
var resourceDirPath = ""
var resourceFtpDir = ""

var ftpType_delall = "delall"	// del:删除的当前全部的
var ftpType_all = "all"	// 直接全部传输
var configFileName = "config.toml"	// 设置文件文件字

var changeUpType string = "none" // 操作更新方式
var upToolType string = "svn" // 更新工具类型svn/git

var failStringList = garray.NewStrArray() // 失败列表
var ftpDataList []*FTP = []*FTP{}		// ftp数据列表
var filterStrList []string  // 过滤字符串列表
var totalAllSize int64 = 0	// 总上传大小

func main() {
	//初始化日志
	utils.InitLog()
	platfromSignStr := ""	// 默认当前文件/目录直接上传 res:有版本文件VersionName test:为获得对应平台xx_test包的内容 ok:拿当前平台包
	ftpType := ""
	paramLen := len(os.Args)
	if (paramLen >= 2) {
		ftpType = os.Args[1]
	}
	if (paramLen >= 3) {
		resourceDirPath = os.Args[2]
	}
	if (paramLen >= 4) {
		resourceFtpDir = os.Args[1]
	}

	flaConfigPath := configFileName

	if !utils.IsFileExists(flaConfigPath) {
		g.Log().Errorf("当前缺少配置文件:%s", flaConfigPath)
		os.Exit(-1)
	}
	isVitallyParam := gCfg().GetBool("initConfig.IsVitallyParam", true)
	if (isVitallyParam == true) {
		isVitallyParamBool := paramLen > 1 && (ftpType == ftpType_all || ftpType == ftpType_delall)
		if (isVitallyParamBool != true) {
			g.Log().Errorf("ftp同步方式必需是: %s", ftpType_all)
			os.Exit(-1)
		}
	}
	g.Log().Infof("开始执行脚本参数操作方式:%s 平台标识参数:%s",ftpType, platfromSignStr)
	readConfig(flaConfigPath)

	if(resourceDirPath == "") {
		resourceDirPath = config.LocalDir
	}

	utils.CheckValueError("LocalDir", resourceDirPath, "")
	if (resourceFtpDir == "") {
		resourceFtpDir = config.FtpDir
	}
	utils.CheckValueError("FtpDir", resourceFtpDir, "")

	ftpMkdDir := resourceFtpDir

	var calcTime = utils.GetTimestamp()
	svnUpDir(config.LocalDir)

	var ftp = new(FTP)

	g.Log().Debugf("Connect Host:%s Port:%d", config.Host, config.Port)
	// connect
	ftp.Connect(config.Host, config.Port)
	// quit
	defer ftp.Quit()
	g.Log().Debugf("Login User:%s", config.User)
	// login
	ftp.Login(config.User, config.Passwd)
	// login failure
	if ftp.Code == 530 {
		g.Log().Error("error:login failure user:%s",config.User)
		os.Exit(-1)
	}
	// pwd
	ftp.Pwd()
	g.Log().Debugf("Pwd code: %v  message:%v", ftp.Code, ftp.Message)
	ftp.mkdirs(ftpMkdDir)

	g.Log().Infof("准备上传:%s\t==>%s", resourceDirPath, resourceFtpDir)

	if (ftpType == ftpType_delall) {
		ftpDelPath := gCfg().GetString("ftpConfig.FtpDelDir", "/")
		if (ftpDelPath ==  "/" || ftpDelPath == "\\") {
			g.Log().Errorf("不可删除根目录，请在config.FtpDelDir配置子级目录:%s", ftpDelPath)
			os.Exit(-3)
		}
		g.Log().Infof("删除ftp目录:%s", ftpDelPath)
		ftp.Rmdir(ftpDelPath) // 还有问题未处理
	} else {
		ftp.ftpRequest(resourceDirPath, resourceFtpDir)
		ftp.fileFtpPut()
		fileBuffer.Reset()
	}
	// quit
	//ftp.Quit()
	if (failStringList.Len() > 0) {
		g.Log().Warningf("上传失败列表:%+v", failStringList)
	}
	for jj := 0; jj < len(ftpDataList); jj++ {
		g.Log().Infof("ftp进程:%d 上传了：%d个", jj, ftpDataList[jj].count)
		ftpDataList[jj].Quit()
	}
	g.Log().Infof("上传结束=====文件总数:%d个\t  总共用时：%s\t 总共上传大小:%s\n", ftpTatleNum,
		utils.FormatTimeSecondAutoDefalut(utils.GetTimestamp() - calcTime), utils.GetCalcTotalSize(totalAllSize))
}

// 更新目录内容
func svnUpDir(pathDir string) {
	if changeUpType != "up" && changeUpType != "all"  {
		return
	}
	svnUpStr := "svn up "
	if upToolType == "git" {
		svnUpStr = "git pull "
	}
	 msgStr, err := utils.GfShell(svnUpStr + pathDir)
	utils.CheckError(err, fmt.Sprintf("更新目录内容%s %s pathDir:%s err:%v", msgStr, svnUpStr, pathDir, err))
}

// 批量创建文件夹
func (ftp *FTP) mkdirs(ftpDir string) {
	ftpDir = strings.Replace(ftpDir, "\\", "/", -1)
	dirList := strings.Split(ftpDir, "/")
	dirPath := ""
	for i := 0; i < len(dirList); i++ {
		dirPath += dirList[i]+ "/"
		if (dirPath == "/") {
			continue;
		}
		ftp.Mkd(dirPath)
	}
}

func (ftp *FTP) ftpRequest(toDirPath string, ftpDirPath string) {
	runtime.GOMAXPROCS(runtime.NumCPU())
	toDirPath = strings.Replace(toDirPath, "\\", "/", -1)
	//toDirPath := dirPath + fileName
	g.Log().Debugf("开始上传文件夹内容 ==>>%s", toDirPath)

	isFile, fileSize := utils.IsFileOrSize(toDirPath)
	if isFile == true {
		ftpDirPath += path.Base(toDirPath)
		g.Log().Infof("开始上传文件:%s\t ftpDirPath:%s总大小：%s", toDirPath, ftpDirPath, utils.GetCalcTotalSize(fileSize))
		ftp.PutPasv(toDirPath, ftpDirPath)
		totalAllSize += fileSize
		return
	}

	var fileTotleNum = 0
	var pathStrRecord = ""
	var totalSize int64 = 0
	filepath.Walk(toDirPath, func(path1 string, file os.FileInfo, err error) error {
		if file == nil {return err}
		if utils.IsFilterStr(path1, filterStrList) == true { return nil }
		localPath := strings.Replace(path1, "\\", "/", -1)
		ftpPath := strings.Replace(localPath, toDirPath, ftpDirPath, -1)
		if file.IsDir() {
			ftp.Mkd(ftpPath)
			return nil
		}
		fileTotleNum ++
		tmpFileSize := file.Size()
		totalSize = totalSize + tmpFileSize
		if pathStrRecord == "" {
			pathStrRecord = fmt.Sprintf("%s,%s,%d",localPath, ftpPath, tmpFileSize)
			fileBuffer.WriteString(pathStrRecord)
		} else {
			fileBuffer.WriteString(fmt.Sprintf("\r\n%s,%s,%d",localPath, ftpPath, tmpFileSize))
		}
		return nil
	})
	ftp.totalSize = totalSize
	//ftpTatleNum += dirTotleNum
	totalAllSize += totalSize
}

// 文件ftp上传
func (mainFtp *FTP)fileFtpPut() {
	var	calcTime = utils.GetTimestamp()
	filePathStr := fileBuffer.String()
	ftpStrList := strings.Split(filePathStr, "\r\n")
	Len := len(ftpStrList)
	if Len == 0 {
		return
	}
	sigleTotalSize := mainFtp.totalSize
	var siglePutSize int64 = 0
	g.Log().Infof("准备ftp上传,需要上传文件数:%d个\t总大小:%s", Len, utils.GetCalcTotalSize(sigleTotalSize))
	listLen := gconv.Int(math.Min(math.Max(config.CpuDouble, gconv.Float64(1)), gconv.Float64(Len)))

	initFtpLoopList(listLen)

	typeChan := make(chan int, listLen)
	startPutTime := utils.GetTimestamp() // 开始上传
	var ii = 0
	//failLen := listLen

	for i := 0; i < listLen; i++ {
		ii = i
		go fileFtpRequestPut(i, ftpStrList[i], typeChan)
	}
	g.Log().Debugf("文件ftp上传进程数:%d", listLen)
	showPathStr := ""
	showLen := Len + 1
	var intervalTime int64 = 300
	intervalCount := 0
	for {
		select {
		case type1 := <-typeChan:
			tmpFtp := ftpDataList[type1]
			showPathStr = tmpFtp.putPath
			if (tmpFtp.workState == "err") {
				tmpFtp.workState = "nil"
				failStringList.Append(tmpFtp.ftpPath)
			}
			siglePutSize += tmpFtp.totalSize

			//if failStringList.Len() == 0 {
			ii++
			if ii >= Len {
				listLen--
				g.Log().Debugf("listLen >>>", listLen, ii)
				// continue
			} else {
				go fileFtpRequestPut(type1, ftpStrList[ii], typeChan)
			}
			calcIntervalTime := utils.GetTimestamp() - startPutTime
			if (calcIntervalTime >= intervalTime && calcIntervalTime % intervalTime == 0) {
				if (intervalCount == 1) {
					g.Log().Warningf("mainFtp.Pwd:%d", intervalCount)
					mainFtp.Pwd()
				}
				intervalCount = 0
			} else {
				intervalCount = 1
			}
			showLen--
			g.Log().Infof("%d\t%s\t%s ", showLen, getSigleSpeed(startPutTime, sigleTotalSize, siglePutSize), showPathStr)
		}
		if listLen == 0 {
			g.Log().Debugf("for listLen", listLen)
			break
		}
	}

	ftpTatleNum += Len
	g.Log().Infof("本次总处理文件数:%d 总共用时:%s", Len, utils.FormatTimeSecondAutoDefalut(utils.GetTimestamp()-calcTime))
}

// 初始ftp列表
func initFtpLoopList(ftpNum int) {
	oldLen := len(ftpDataList)
	for iloop := 0; iloop < ftpNum; iloop++ {
		var fileFtp *FTP
		if (iloop < oldLen) {
			fileFtp = ftpDataList[iloop]
			fileFtp.workState = "ok"
		} else {
			fileFtp = ftpInit()
			fileFtp.orderId = iloop
			ftpDataList = append(ftpDataList, fileFtp)
		}
	}
}

func getSigleSpeed(startTime int64, sigleTotalSize, siglePutSize int64) string {
	calcTime := utils.GetTimestamp() - startTime
	if (calcTime < 1) {
		calcTime = 1
	}
	speedSize := siglePutSize / calcTime
	wantTime := (sigleTotalSize - siglePutSize) / speedSize
	if (wantTime == 0) {
		return fmt.Sprintf("%s/秒 即将结束", utils.GetCalcTotalSize(speedSize))
	}
	return fmt.Sprintf("%s/秒 预计时间:%s", utils.GetCalcTotalSize(speedSize), utils.FormatTimeSecondAutoDefalut(wantTime))
}

func dirFtpRequestMkd(typeChange int, dirPathStr string,typeChan chan <- int) {
	defer func() { typeChan <- typeChange }()
	if dirPathStr == "" {
		return
	}
	ftp1 := ftpDataList[typeChange]
	ftp1.putPath = dirPathStr
	ftp1.Mkd(dirPathStr)
	ftp1.count += 1
}
func fileFtpRequestPut(typeChange int, pathStr string, typeChan chan <- int) {
	defer func() { typeChan <- typeChange }()
	if pathStr == "" {
		//typeChan <- typeChange
		return
	}
	ftp1 := ftpDataList[typeChange]
	pathStrList := strings.Split(pathStr, ",")
	if len(pathStrList) < 2 {
		g.Log().Errorf("拼接传输字符串错误:len(pathStrList) < 2: %s", pathStr)
		//typeChan <- typeChange
		return
	}
	//if (ftp1.workState == "nil") {
	//	g.Log().Warningf("pathStrList===:%+v", pathStrList)
	//}
	path1 := pathStrList[0]
	path2 := pathStrList[1]
	// ftp.Request("TYPE I")
	ftp1.putPath = path1
	ftp1.ftpPath = pathStr
	ftp1.totalSize = gconv.Int64(pathStrList[2])
	//g.Log().Infof("%d\t:%s", orderId, path1)
	ftp1.PutPasv(path1, path2)
	//ftp.Quit()
	ftp1.count += 1
	//typeChan <- typeChange
}

func gCfg()*gcfg.Config  {
	return g.Cfg(configFileName)
}
// 读取配置
func readConfig(configPath string) {
	g.Log().Debugf("读取配置:%s", configPath)
	config = Config{}
	config.Host = gCfg().GetString("ftpConfig.Host", "")
	config.Port = gCfg().GetInt("ftpConfig.Port", 0)
	config.User = gCfg().GetString("ftpConfig.User", "")
	config.Passwd = gCfg().GetString("ftpConfig.Passwd", "")
	config.LocalDir = gCfg().GetString("ftpConfig.LocalDir", "")
	config.FtpDir = gCfg().GetString("ftpConfig.FtpDir", "")
	config.CpuDouble = gCfg().GetFloat64("initFtpConfig.CpuDouble", 5)
	g.Log().Debugf("读取配置:%+v", config)

	utils.CheckValueError("ftpConfig.Host", config.Host, "")
	utils.CheckValueError("ftpConfig.User", config.User, "")
	utils.CheckValueError("ftpConfig.Passwd", config.Passwd, "")
	utils.CheckValueError("ftpConfig.LocalDir", config.LocalDir, "")
	utils.CheckValueError("ftpConfig.FtpDir", config.FtpDir, "")
	utils.CheckValueError("ftpConfig.Port", gconv.String(config.Port), "0")

	filterStrList := gCfg().GetStrings("initFtpConfig.FilterStrList", []string{"+"})

	changeUpType = gCfg.GetString("initFtpConfig.ChangeUpType", "none")
	upToolType = gCfg.GetString("initFtpConfig.UpToolType", "")
	if upToolType == "" {
		changeUpType = "none"
	}
}

// 初始ftp
func ftpInit() *FTP {
	ftpInit := new(FTP)
	// connect
	ftpInit.Connect(config.Host, config.Port)
	// login
	ftpInit.Login(config.User, config.Passwd)
	// login failure
	if ftpInit.Code == 530 {
		g.Log().Error("error: login failure")
		os.Exit(-1)
	}
	ftpInit.count = 0;
	ftpInit.failCount = 0;
	ftpInit.workState = "ok"
	// pwd
	ftpInit.Pwd()
	return ftpInit
}
func (ftp *FTP) Connect(host string, port int) {
	ftp.StateStr = "连接"
	ftp.host = host
	ftp.port = port
	addr := fmt.Sprintf("%s:%d", host, port)
	ftp.conn, _ = net.Dial("tcp", addr)
	ftp.Response()
}

func (ftp *FTP) Login(user, passwd string) {
	ftp.user = user
	ftp.StateStr = "登录-输入User"
	g.Log().Debugf("准备User:%s!", ftp.user)
	ftp.Request("USER " + user)
	if (ftp.Code != 331) {
		utils.CheckError(gerror.New("用户不存在"), ftp.user)
	}
	ftp.StateStr = "登录-输入Pass"
	g.Log().Debug("准备Pass")
	ftp.Request("PASS " + passwd)
	ftp.passwd = passwd
	if ftp.Code == 230 {
		// fmt.Println("登录成功：", user)
		return
	}
	g.Log().Errorf("ftp登录失败use:%s code:%d err:%s", user, ftp.Code, ftp.Message)
	os.Exit(-1)
	return
}

func (ftp *FTP) Response() (code int, message string) {
	ret := make([]byte, 1024)
	n, _ := ftp.conn.Read(ret)
	msg := string(ret[:n])
	if len(msg) <= 3 {
		str := strings.Replace(ftp.cmd, resourceFtpDir, resourceDirPath, -1)
		g.Log().Errorf("ftp%s错误: str:%s msg:%s", ftp.StateStr, str, msg)
		return 450, ""
	}
	code, _ = strconv.Atoi(msg[:3])
	message = msg[4 : len(msg)-2]
	g.Log().Debugf("<cmd> %v", ftp.cmd)
	g.Log().Debugf("<code> %d", code)
	g.Log().Debugf("<message> %v", message)
	return
}

func (ftp *FTP) Request(cmd string) {
	ftp.conn.Write([]byte(cmd + "\r\n"))
	ftp.cmd = cmd
	ftp.Code, ftp.Message = ftp.Response()
}

func (ftp *FTP) Pasv() {
	ftp.Request("PASV")
}

func (ftp *FTP) Pwd() {
	ftp.Request("PWD")
}

func (ftp *FTP) Cwd(path string) {
	ftp.Request("CWD " + path)
}

func (ftp *FTP) Mkd(path string) {
	ftp.StateStr = "创建目录"
	ftp.Request("MKD " + path)
}

func (ftp *FTP) Mdel(path string) {
	ftp.Request("prompt")
	//g.Log().Debug("Mdel:%d  %s",ftp.Code, ftp.Message)
	ftp.Request("MDELETE " + path)
	//g.Log().Debug("MDELETE:%d  %s",ftp.Code, ftp.Message)
}
func (ftp *FTP) Rmdir(path string) {
	//ftp.Request("prompt")
	//g.Log().Debug("Mdel:%d  %s",ftp.Code, ftp.Message)
	ftp.Request("RMDIR " + path)
}

func (ftp *FTP) Size(path string) (size int) {
	ftp.Request("SIZE " + path)
	size, _ = strconv.Atoi(ftp.Message)
	return
}

func (ftp *FTP) List() {
	ftp.Pasv()
	ftp.Request("LIST")
}

func (ftp *FTP) Quit() {
	ftp.StateStr = "退出"
	ftp.Request("QUIT")
	ftp.conn.Close()
}

func (ftp *FTP) PutPasv(localPath,Pathname string) {
	ftp.StateStr = "上传数据"
	ftp.Request("TYPE I")
	ftp.Pasv()
	con, err := ftp.newRequest("STOR", Pathname)
	if err != nil {
		ftp.failCount += 1
		ftp.workState = "err"
		return
	}
	File, err := os.Open(localPath)
	if err != nil {
		con.Close()
		return
	}
	io.Copy(con, File)
	File.Close()
	con.Close()
	ftp.Response()
}

func (ftp *FTP) newRequest(cmd, Pathname string) (net.Conn, error) {
	ftp.getport()
	con, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ftp.host, ftp.pasv))
	if err != nil {
		return nil, err
	}
	ftp.Request(fmt.Sprintf("%s %s", cmd, Pathname))
	if ftp.Code != 150 && ftp.Code != 125{
		//g.Log().Errorf("ftp 数据失败:%d orderId:%d path:%s", ftp.Code, ftp.orderId,ftp.putPath)
		g.Log().Errorf("ftp 数据失败:%d", ftp.Code)
		con.Close()
		return nil, gerror.New("create data link error.")
	}
	return con, nil
}
func (ftp *FTP) getport() {
	start, end := strings.Index(ftp.Message, "("), strings.Index(ftp.Message, ")")
	s := strings.Split(ftp.Message[start:end], ",")
	l1, _ := strconv.Atoi(s[len(s)-2])
	l2, _ := strconv.Atoi(s[len(s)-1])
	ftp.pasv = l1*256 + l2
}

// 获得文件夹路径        去除路径文件部分
func string_remove(str string) (file string) {
	return path.Dir(str)
}

